﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ITAP.ContourBuilder.Constraints;
using ITAP.Core.Modelling.Primitives;

using System.Drawing;
using ITAP.Core.Calculations;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using ITAP.ContourBuilder.Components.Dimensions;
using System.Globalization;
using ru.tcl.dxf;
using System.Windows.Forms;
using ITAP.ContourBuilder.Utils;
using ITAP.Core.Modelling;

namespace ITAP.ContourBuilder.Components
{
    [Serializable]
    public class ContourContainer : IContourComponent
    {

        public bool Modified
        {
            get { return false; }
            set { }
        }
        private List<IContourComponent> selectedComponentsStack;

        public List<IContourComponent> SelectedComponentsStack
        {
            get { return selectedComponentsStack; }
            set { selectedComponentsStack = value; }
        }

        private bool isPrimitiveDependsOnDisplayParameters = false;

        public bool IsPrimitiveDependsOnDisplayParameters
        {
            get { return isPrimitiveDependsOnDisplayParameters; }
        }

        private string name;

        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        public RectangleF InnerBoundingBox
        {
            get
            {
                int counter = 0;
                RectangleF bbox = RectangleF.Empty;
                foreach (IContourComponent innerComponent in this.innerComponents)
                {
                    if (!innerComponent.HasBoundingBox)
                        continue;

                    if (counter == 0)
                    {
                        bbox = innerComponent.BoundingBox;
                        counter++;
                        continue;
                    }
                    bbox = RectangleF.Union(bbox, innerComponent.BoundingBox);
                    counter++;
                }
                return bbox;
            }
        }

        /// <summary>
        /// Оставил для создания шейпов - там скорее всего не надо будет задавать начальные размеры контейнера, т.к.
        /// они будут зависеть от редактора.
        /// </summary>
        public ContourContainer()
        {
            this.innerComponents = new List<IContourComponent>();
            this.connectedComponents = new List<IContourComponent>();
            this.acceptedTypesToConnect = new List<Type>();
            this.selectedComponentsStack = new List<IContourComponent>();
            this.componentsInTolerance = new List<IContourComponent>();
        }

        private bool hasBounds = true;

        public virtual bool HasBounds
        {
            get { return hasBounds; }
            set { hasBounds = value; }
        }

        private float rightIndent;

        public float RightIndent
        {
            get { return rightIndent; }
            set { rightIndent = value; }
        }
        private float upIndent;

        public float UpIndent
        {
            get { return upIndent; }
            set { upIndent = value; }
        }
        private float leftIndent;

        public float LeftIndent
        {
            get { return leftIndent; }
            set { leftIndent = value; }
        }
        private float downIndent;

        public float DownIndent
        {
            get { return downIndent; }
            set { downIndent = value; }
        }

        #region IContourComponent Members

        private List<IContourComponent> connectedComponents;
        public IEnumerable<IContourComponent> ConnectedComponents
        {
            get { return connectedComponents; }
        }
        private List<IContourComponent> innerComponents;
        public IEnumerable<IContourComponent> InnerComponents
        {
            get { return innerComponents; }
        }

        public IEnumerable<IContourConstraint> Constraints
        {
            get { throw new NotImplementedException(); }
        }

        private List<IContourComponent> componentsInTolerance;
        public ICollection<IContourComponent> ComponentsInTolerance
        {
            get { return componentsInTolerance; }
        }

        private List<Type> acceptedTypesToConnect;
        public ICollection<Type> AcceptedTypesToConnect
        {
            get { return acceptedTypesToConnect; }
        }

        public IPrimitive Primitive
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        public RectangleF BoundingBox
        {
            get
            {
                RectangleF innerBbox = this.InnerBoundingBox;
                RectangleF bbox = new RectangleF(innerBbox.X - leftIndent, innerBbox.Y - downIndent, innerBbox.Width + leftIndent + rightIndent, innerBbox.Height + downIndent + upIndent);
                return bbox;
            }
        }


        public void Update()
        {
            foreach (IContourComponent innerComponent in this.innerComponents)
                innerComponent.Update();
        }

        public IContourComponent ConnectTo(IContourComponent component)
        {
            if (!this.connectedComponents.Contains(component))
            {
                this.connectedComponents.Add(component);
                return this;
            }
            else
                return null;
        }

        public IContourComponent ConnectToEnded(IEndedContourComponent component, ITAP.Core.Modelling.Primitives.EndType endToConnect)
        {
            throw new NotImplementedException();
        }

        public IContourComponent DisconnectFrom(IContourComponent component)
        {
            if (this.connectedComponents.Contains(component))
            {
                this.connectedComponents.Remove(component);
                return this;
            }
            else
                return null;
        }

        public IContourComponent Parent
        {
            get
            {
                return this;
            }
            set
            {
            }
        }

        public int SerialNumber
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        public ModelPoint Position
        {
            get
            {
                RectangleF bbox = this.BoundingBox;
                return new ModelPoint(bbox.X + bbox.Width / 2, bbox.Y + bbox.Height / 2);
            }
        }

        public void MoveTo(ITAP.Core.Modelling.ModelPoint destination)
        {
            ModelPoint currentPosition = this.Position;
            ModelVector offset = new ModelVector(currentPosition, destination);
            this.MoveWithOffset(offset);
        }

        public void MoveWithOffset(ITAP.Core.Modelling.ModelVector offset)
        {
            foreach (IContourComponent model in this.innerComponents)
                model.MoveWithOffset(offset);
        }

        public bool EnableConstraints
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        public bool Selected
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        public void SelectByPoint(ITAP.Core.Modelling.ModelPoint point)
        {
            throw new NotImplementedException();
        }

        public double Tolerance
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        #endregion

        #region IContourComponent Members


        public void AcceptVisitor(IContourVisitor visitor)
        {
            visitor.Visit(this);
        }

        #endregion

        #region IContourVisitor Members

        public void Visit(ContourModel model)
        {
            // throw new NotImplementedException();
        }

        public void Visit(ContourContainer container)
        {
            // throw new NotImplementedException();
        }

        public virtual void Visit(ContourElement element)
        {
            //  throw new NotImplementedException();
        }

        public virtual void Visit(ContourConnector connector)
        {
            // throw new NotImplementedException();
        }

        #endregion

        #region IContourComponent Members


        public void SetDisplayParameters(int screenWidth, int screenHeight, RectangleF modelRectangle, ITAP.Core.Calculations.ModelCoordinateConverter coordinateConverter, ModelPoint cameraCenter)
        {
            IEnumerable<IContourComponent> innerComponents = this.GetAllInnerComponents();
            foreach (IContourComponent innerComponent in innerComponents)
                if (innerComponent.IsPrimitiveDependsOnDisplayParameters)
                    innerComponent.SetDisplayParameters(screenWidth, screenHeight, modelRectangle, coordinateConverter, cameraCenter);
        }

        #endregion

        #region ICloneable Members

        public object Clone()
        {
            MemoryStream stream = new MemoryStream();
            BinaryFormatter formatter = new BinaryFormatter();

            formatter.Serialize(stream, this);
            stream.Seek(0, SeekOrigin.Begin);
            object clone = formatter.Deserialize(stream);
            stream.Close();
            return clone;
        }

        #endregion



        #region IContourComponent Members


        public bool IsInTolerance(ModelPoint point, double tolerance)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region IContourComponent Members


        public bool IsComponentDotted
        {
            get { return false; }
        }

        #endregion

        #region IContourComponent Members


        public bool CanSelectByPoint(ModelPoint point)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region IContourVisitor Members


        public void Visit(ContourElementHook hook)
        {
            //throw new NotImplementedException();
        }

        #endregion

        #region IContourVisitor Members


        public virtual void Visit(ContourText text)
        {
            //throw new NotImplementedException();
        }

        #endregion

        #region IContourVisitor Members


        public void Visit(ContourCommentLine commentLine)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region IContourVisitor Members


        public virtual void Visit(Dimension dimension)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region IContourVisitor Members


        public virtual void Visit(ContourHole hole)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region IContourComponent Members


        public int Id
        {
            get
            {
                return -1;
            }
            set
            {
            }
        }

        #endregion

        #region IContourComponent Members


        public bool HasBoundingBox
        {
            get
            {
                return true;
            }
            set
            {
            }
        }

        #endregion

        public ModelPoint AbsoluteToOuter(ModelPoint point)
        {
            RectangleF outerBbox = this.BoundingBox;
            return new ModelPoint(point.X - outerBbox.X, point.Y - outerBbox.Y);

        }

        public ModelPoint OuterToAbsolute(ModelPoint point)
        {
            RectangleF outerBbox = this.BoundingBox;
            return new ModelPoint(point.X + outerBbox.X, point.Y + outerBbox.Y);
        }

        CultureInfo culture = new CultureInfo("en-US");

        public List<string> GetAnzData()
        {
            List<string> anz = new List<string>();
            anz.Add(GetAnzNumber(this.BoundingBox.Width) + " " + GetAnzNumber(this.BoundingBox.Height) + " 0");
            anz.Add("1 0 0 2");
            anz.Add("0.000000");
            anz.Add("0.000000");
            anz.Add("1 0 0 2");
            anz.Add(GetAnzNumber(this.BoundingBox.Width));
            anz.Add(GetAnzNumber(this.BoundingBox.Height));
            foreach (IContourComponent component in this.innerComponents)
            {
                if (component is ContourModel)
                {
                    ContourModel model = component as ContourModel;
                    foreach (ContourElement element in model)
                    {
                        if (element.Height != 0)
                        {
                            Circle circle = element.Primitive as Circle;
                            anz.Add("3 0 0 2");
                            ModelPoint center = this.AbsoluteToOuter(circle.Center);
                            ModelPoint startPoint = this.AbsoluteToOuter(circle.StartPoint);
                            ModelPoint endPoint = this.AbsoluteToOuter(circle.EndPoint);

                            anz.Add(GetAnzNumber(center.X));
                            anz.Add(GetAnzNumber(center.Y));
                            anz.Add(GetAnzNumber(circle.Radius));
                            if (element.Height > 0)
                            {
                                anz.Add(GetAnzNumber(circle.StartAngle.Value));
                                anz.Add(GetAnzNumber(circle.EndAngle.Value));
                                anz.Add(GetAnzNumber(startPoint.X));
                                anz.Add(GetAnzNumber(startPoint.Y));
                                anz.Add(GetAnzNumber(endPoint.X));
                                anz.Add(GetAnzNumber(endPoint.Y));
                            }
                            else
                            {
                                anz.Add(GetAnzNumber(circle.EndAngle.Value));
                                anz.Add(GetAnzNumber(circle.StartAngle.Value));
                                anz.Add(GetAnzNumber(endPoint.X));
                                anz.Add(GetAnzNumber(endPoint.Y));
                                anz.Add(GetAnzNumber(startPoint.X));
                                anz.Add(GetAnzNumber(startPoint.Y));
                            }
                        }
                        else
                        {
                            Line line = element.Primitive as Line;
                            ModelPoint startPoint = this.AbsoluteToOuter(line.StartPoint);
                            ModelPoint endPoint = this.AbsoluteToOuter(line.EndPoint);
                            anz.Add("2 0 0 2");
                            anz.Add(GetAnzNumber(startPoint.X));
                            anz.Add(GetAnzNumber(startPoint.Y));
                            anz.Add(GetAnzNumber(endPoint.X));
                            anz.Add(GetAnzNumber(endPoint.Y));
                        }
                    }
                }
            }
            anz.Add("7 0 0 7");
            anz.Add(GetAnzNumber(this.BoundingBox.Width / 2));
            anz.Add(GetAnzNumber(this.BoundingBox.Height / 2));
            return anz;
        }

        private string GetAnzNumber(double number)
        {
            return number.ToString("0.000000", culture);
        }

        private string GetAnzNumber(float number)
        {
            return number.ToString("0.000000", culture);
        }

        public ModelPoint AbsoluteToInner(ModelPoint point)
        {
            RectangleF innerBbox = this.InnerBoundingBox;
            return new ModelPoint(point.X - innerBbox.X, point.Y - innerBbox.Y);
        }

        public ModelPoint InnerToAbsolute(ModelPoint point)
        {
            RectangleF innerBbox = this.InnerBoundingBox;
            return new ModelPoint(point.X + innerBbox.X, point.Y + innerBbox.Y);
        }

        public double GetPerimeter()
        {
            double perimeter = 0;
            foreach (IContourComponent component in this.innerComponents)
                if (component is ContourModel)
                {
                    ContourModel model = component as ContourModel;
                    foreach (ContourElement element in model)
                        perimeter += element.Length;
                }
            return perimeter;
        }

        public void SaveToDxf(string fileName)
        {
            /* 
             *  0
                SECTION
                  2
                ENTITIES
                  0
                LINE
                  8
                0
                10
                1.0
                20
                2.0
                11
                3.0
                21
                4.0
                  0
                ENDSEC
                  0
                EOF 
             */

            CultureInfo en = new CultureInfo("en-US");

            List<string> fileContent = new List<string>();
            fileContent.Add("0");
            fileContent.Add("SECTION");
            fileContent.Add("  2");
            fileContent.Add("ENTITIES");
            foreach (IContourComponent component in this.innerComponents)
            {
                if (component is ContourModel)
                {
                    ContourModel model = component as ContourModel;
                    foreach (ContourElement element in model)
                    {
                        fileContent.Add("  0");
                        if (element.Height == 0)
                        {
                            fileContent.Add("LINE");
                            fileContent.Add("  8");
                            fileContent.Add("0");
                            //координаты точки начала
                            fileContent.Add("10");
                            ModelPoint innerStartPoint = this.AbsoluteToInner(element.StartPoint);
                            ModelPoint innerEndPoint = this.AbsoluteToInner(element.EndPoint);
                            fileContent.Add(innerStartPoint.X.ToString(en));
                            fileContent.Add("20");
                            fileContent.Add(innerStartPoint.Y.ToString(en));
                            //координаты точки конца
                            fileContent.Add("11");
                            fileContent.Add(innerEndPoint.X.ToString(en));
                            fileContent.Add("21");
                            fileContent.Add(innerEndPoint.Y.ToString(en));
                        }
                        else
                        {
                            Circle circle = element.Primitive as Circle;
                            fileContent.Add("ARC");
                            fileContent.Add("  8");
                            fileContent.Add("0");
                            //центр
                            ModelPoint innerCenter = this.AbsoluteToInner(circle.Center);
                            fileContent.Add("10");
                            fileContent.Add(innerCenter.X.ToString(en));
                            fileContent.Add("20");
                            fileContent.Add(innerCenter.Y.ToString(en));
                            //радиус
                            fileContent.Add("40");
                            fileContent.Add(circle.Radius.ToString(en));
                            if (element.Height > 0)
                            {
                                //начальный угол
                                fileContent.Add("50");
                                fileContent.Add((circle.StartAngle.Value * 180 / Math.PI).ToString(en));
                                //конечный угол
                                fileContent.Add("51");
                                fileContent.Add((circle.EndAngle.Value * 180 / Math.PI).ToString(en));
                            }
                            else
                            {
                                //начальный угол
                                fileContent.Add("50");
                                fileContent.Add((circle.EndAngle.Value * 180 / Math.PI).ToString(en));
                                //конечный угол
                                fileContent.Add("51");
                                fileContent.Add((circle.StartAngle.Value * 180 / Math.PI).ToString(en));
                            }
                        }
                    }
                }
            }

            fileContent.Add("  0");
            fileContent.Add("ENDSEC");
            fileContent.Add("  0");
            fileContent.Add("EOF");

            File.WriteAllLines(fileName, fileContent.ToArray());
        }

        public void LoadFromDxf(string fileName)
        {
            java.io.File file = new java.io.File(fileName);

            DXFDocument doc = new DXFDocument();
            doc.readDXF(file);

            java.util.Enumeration items = doc.getEntities().items();

            List<IPrimitive> primitives = new List<IPrimitive>();

            while (items.hasMoreElements())
            {
                object obj = items.nextElement();
                if (obj is ru.tcl.dxf.entities.Line)
                {
                    ru.tcl.dxf.entities.Line line = obj as ru.tcl.dxf.entities.Line;

                    Line _line = new Line(new ModelPoint(line.getStart().getX(), line.getStart().getY()),
                                         new ModelPoint(line.getEnd().getX(), line.getEnd().getY()));
                    primitives.Add(_line);
                }
                else if (obj is ru.tcl.dxf.entities.Arc)
                {
                    ru.tcl.dxf.entities.Arc arc = obj as ru.tcl.dxf.entities.Arc;
                    double _startAngle = arc.getStartAngle();
                    double _endAngle = arc.getEndAngle();
                    Angle startAngle = new Angle(_startAngle * Math.PI / 180);
                    Angle endAngle = new Angle(_endAngle * Math.PI / 180);

                    Circle circle = new Circle(new ModelPoint(arc.getCenter().getX(), arc.getCenter().getY()),
                                               startAngle, endAngle, arc.getRadius(), 1);
                    primitives.Add(circle);
                }
                else if (obj is ru.tcl.dxf.entities.Polyline)
                {
                    java.util.Enumeration vertexes = (obj as ru.tcl.dxf.entities.Polyline).items();
                    List<ru.tcl.dxf.entities.Vertex> verteces = new List<ru.tcl.dxf.entities.Vertex>();
                    while (vertexes.hasMoreElements())
                    {
                        object elem = vertexes.nextElement();

                        if (elem is ru.tcl.dxf.entities.Vertex)
                            verteces.Add(elem as ru.tcl.dxf.entities.Vertex);
                    }
                    if (verteces.Count == 0)
                        return;
                    ru.tcl.dxf.entities.Vertex prevVertex = verteces[0];
                    for (int i = 1; i < verteces.Count; i++)
                    {
                        double bulge = prevVertex.getBulge();
                        if (bulge == 0)
                            this.AddLine(prevVertex, verteces[i], primitives);
                        else
                            this.AddArc(prevVertex, verteces[i], primitives);

                        prevVertex = verteces[i];
                    }

                    // Иногда последний примитив может быть нулевой длины, иногда нет. Поэтому необходимо после получения списка примитивов 
                    // обработать их, удалив примитивы с нулевой длиной и т.д. Методы для этого есть в DxfToModelPrimitiveAdapter.
                    double _bulge = verteces[verteces.Count - 1].getBulge();
                    if (_bulge == 0)
                        this.AddLine(verteces[verteces.Count - 1], verteces[0], primitives);
                    else
                        this.AddArc(verteces[verteces.Count - 1], verteces[0], primitives);
                }
            }

            this.RemoveAllInnerComponents();

            ContourModel model = ContourElementFactory.CreateModel(primitives);

            this.AddInnerComponent(model);
        }

        private void AddArc(ru.tcl.dxf.entities.Vertex vertex1, ru.tcl.dxf.entities.Vertex vertex2, List<IPrimitive> primitives)
        {
            double bulge = vertex1.getBulge();
            double c = Math.Sqrt(Math.Pow(vertex1.getLocation().getX() - vertex2.getLocation().getX(), 2) + Math.Pow(vertex1.getLocation().getY() - vertex2.getLocation().getY(), 2));
            double prevX = vertex1.getLocation().getX();
            double prevY = vertex1.getLocation().getY();
            double x = vertex2.getLocation().getX();
            double y = vertex2.getLocation().getY();

            ModelVector vector = new ModelVector(new ModelPoint(prevX, prevY), new ModelPoint(x, y));

            double s = c / 2 * Math.Abs(bulge);

//            double radius = (Math.Pow(c / 2, 2) + Math.Pow(s, 2)) / (2 * s);

            ModelPoint startPoint;
            ModelPoint endPoint;
            startPoint = new ModelPoint(prevX, prevY);
            endPoint = new ModelPoint(x, y);

            Circle circle = new Circle(startPoint, endPoint, s * Math.Sign(bulge));
            primitives.Add(circle);
        }

        private void AddLine(ru.tcl.dxf.entities.Vertex vertex1, ru.tcl.dxf.entities.Vertex vertex2, List<IPrimitive> primitives)
        {
            ModelPoint startPoint = new ModelPoint(vertex1.getLocation().getX(), vertex1.getLocation().getY());
            ModelPoint endPoint = new ModelPoint(vertex2.getLocation().getX(), vertex2.getLocation().getY());
            Line line = new Line(startPoint, endPoint);
            primitives.Add(line);
        }

        public ContourContainer GetInvertContainer()
        {
            return ContourUtils.GetInvertContainer(this);
        }

        public ContourContainer GetOffsetContainer()
        {
            ContourContainer result = ContourElementFactory.CreateContainer(this.rightIndent, this.upIndent, this.leftIndent, this.downIndent);
            foreach (IContourComponent component in this.innerComponents)
            {
                if (component is ContourModel)
                {
                    ContourModel model = component as ContourModel;
                    ContourModel offsetModel = ContourUtils.GetOffsetModel(model, false);
                    result.AddInnerComponent(offsetModel);
                }
            }
            return result;
        }
    }
}
